/*   The MIT License
 *   
 *   Carina Engine
 *   Copyright (c) 2009 2010 2011 2012 Zdravko Velinov
 *   
 *   Permission is hereby granted, free of charge, to any person obtaining a copy
 *   of this software and associated documentation files (the "Software"), to deal
 *   in the Software without restriction, including without limitation the rights
 *   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *   copies of the Software, and to permit persons to whom the Software is
 *   furnished to do so, subject to the following conditions:
 *
 *   The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *   THE SOFTWARE.
 */

#ifndef LIGHTING_HH_
#define LIGHTING_HH_

#include "carina/common/global.hh"
#include "carina/common/memory.hh"
#include "carina/node-manager.hh"
#include "carina/renderer-base.hh"
#include "carina/resources.hh"
#include "carina/common/exception.hh"
#include "carina/math.hh"

namespace Carina
{
class Node;
class ResourceManager;
class Drawable;
class Scene;
class ConfigurableObject;

class LightSource: public Configurable
{
    ShaderProgramPtr    m_LightShader;
public:
    virtual ~LightSource() {}

    virtual bool isGlobalLightSource()=0;

	CE_NO_ATTRIBUTES(CE_POLYMORPHIC, NullBaseClass)
};

class SpatialLightSource: public LightSource
{
public:
    virtual ~SpatialLightSource() {}

    virtual bool isGlobalLightSource() { return false; }

    virtual void render(Scene& scene, const Matrix4& tmat, const TexturePtr& albedo, const TexturePtr& normal, const TexturePtr& depth)=0;

    CE_NO_ATTRIBUTES(CE_POLYMORPHIC, LightSource);
};

class GlobalLightSource: public LightSource
{
public:
    virtual ~GlobalLightSource() {}

    virtual bool isGlobalLightSource() { return true; }

    virtual void render(Scene& scene, const TexturePtr& albedo, const TexturePtr& normal, const TexturePtr& depth)=0;

    CE_NO_ATTRIBUTES(CE_POLYMORPHIC, LightSource);
};

typedef shared_ptr<LightSource> LightSourcePtr;
typedef shared_ptr<GlobalLightSource> GlobalLightSourcePtr;
typedef shared_ptr<const GlobalLightSource> GlobalLightSourceConstPtr;

typedef shared_ptr<SpatialLightSource> SpatialLightSourcePtr;

class LightSourceNode: public Node, public enable_shared_from_this<LightSourceNode>
{
    SpatialLightSourcePtr   m_LightSource;
    Matrix4                 m_WorldMatrix;
public:
    LightSourceNode(const SpatialLightSourcePtr& light_source);

    virtual void draw(Scene& scene);

    virtual void notifyTransform(const Matrix4& wmat);

    virtual ConfigurablePtr getConfigurable();
    virtual ConfigurableConstPtr getConfigurable() const;

    void render(Scene& scene, const TexturePtr& albedo, const TexturePtr& normal, const TexturePtr& depth);
};

typedef shared_ptr<LightSourceNode> LightSourceNodePtr;

// TODO: optimize

class Spotlight: public SpatialLightSource
{
    Vector3             m_Color,
                        m_Direction,
                        m_Attenuation;
    float               m_Exponent,
                        m_CosCutOff;

    ShaderProgramPtr    m_Prog;
    InputLayoutPtr      m_Layout;
    VideoBufferPtr      m_FullScreenQuad;

    TextureVarPtr       m_uAlbedoBuffer,
                        m_uNormalVectorBuffer,
                        m_uDepthBuffer;
    UniformVarPtr       m_uColor,
                        m_uLightPosition,
                        m_uDirection,
                        m_uExponent,
                        m_uCosCutOff,
                        m_uAttenuation,
                        m_uInvTransformMatrix,
                        m_uCameraPosition;
    bool                m_MSAA;
public:
    Spotlight(const ShaderProgramPtr& prog, const InputLayoutPtr& layout, const VideoBufferPtr& fsq);

    virtual void render(Scene& scene, const Matrix4& tmat, const TexturePtr& albedo, const TexturePtr& normal, const TexturePtr& depth);
protected:
    CE_DECLARE_ATTRIBUTE(Vector3, CE_RANGE_CLAMPED_POSITIVE, Color, "Color")
    CE_DECLARE_ATTRIBUTE(Vector3, CE_RANGE_NONE, Attenuation, "Attenuation")
    CE_DECLARE_ATTRIBUTE(float, CE_RANGE_NONE, Exponent, "Exponent")
    CE_DECLARE_ATTRIBUTE(float, CE_RANGE_CLAMPED, CosCutOff, "Cosine cut-off")

    CE_ATTRIBUTES(CE_POLYMORPHIC, SpatialLightSource,
               CE_CREATE_ATTRIBUTE(Color),
               CE_CREATE_ATTRIBUTE(Attenuation),
               CE_CREATE_ATTRIBUTE(Exponent),
               CE_CREATE_ATTRIBUTE(CosCutOff))
};

typedef shared_ptr<Spotlight> SpotlightPtr;

class PointLight: public SpatialLightSource
{
    ShaderProgramPtr    m_Prog;
    InputLayoutPtr      m_Layout;
    VideoBufferPtr      m_FullScreenQuad;

    TextureVarPtr       m_uAlbedoBuffer,
                        m_uNormalVectorBuffer,
                        m_uDepthBuffer;
    UniformVarPtr       m_uColor,
                        m_uLightPosition,
                        m_uAttenuation,
                        m_uInvTransformMatrix,
                        m_uCameraPosition;
    bool                m_MSAA;
public:
    PointLight(const ShaderProgramPtr& prog, const InputLayoutPtr& layout, const VideoBufferPtr& fsq);
    virtual ~PointLight() {}

    virtual void render(Scene& scene, const Matrix4& tmat, const TexturePtr& albedo, const TexturePtr& normal, const TexturePtr& depth);
protected:
    CE_DECLARE_ATTRIBUTE(Vector3, CE_RANGE_CLAMPED_POSITIVE, Color, "Color")
    CE_DECLARE_ATTRIBUTE(Vector3, CE_RANGE_NONE, Attenuation, "Attenuation")

    CE_ATTRIBUTES(CE_POLYMORPHIC, SpatialLightSource,
               CE_CREATE_ATTRIBUTE(Color),
               CE_CREATE_ATTRIBUTE(Attenuation))
};

typedef shared_ptr<PointLight> PointLightPtr;

class DirectionalLight: public GlobalLightSource
{
    ShaderProgramPtr    m_Prog;
    InputLayoutPtr      m_Layout;
    VideoBufferPtr      m_FullScreenQuad;

    TextureVarPtr       m_uAlbedoBuffer,
                        m_uNormalVectorBuffer,
                        m_uDepthBuffer;
    UniformVarPtr       m_uColor,
                        m_uDirection,
                        m_uInvTransformMatrix,
                        m_uCameraPosition;
    bool                m_MSAA;
public:
    DirectionalLight(const ShaderProgramPtr& prog, const InputLayoutPtr& layout, const VideoBufferPtr& fsq);
    virtual ~DirectionalLight() {}

    virtual void render(Scene& scene, const TexturePtr& albedo, const TexturePtr& normal, const TexturePtr& depth);
protected:
    CE_DECLARE_ATTRIBUTE(Vector3, CE_RANGE_CLAMPED_POSITIVE, Color, "Color")
    CE_DECLARE_ATTRIBUTE(Vector3, CE_RANGE_NORMALIZED, Direction, "Direction")

    CE_ATTRIBUTES(CE_POLYMORPHIC, GlobalLightSource,
               CE_CREATE_ATTRIBUTE(Color),
               CE_CREATE_ATTRIBUTE(Direction))
};

typedef shared_ptr<DirectionalLight> DirectionalLightPtr;

class AmbientLight: public GlobalLightSource
{
    ShaderProgramPtr    m_Prog;
    InputLayoutPtr      m_Layout;
    VideoBufferPtr      m_FullScreenQuad;

    TextureVarPtr       m_uAlbedoBuffer;
    UniformVarPtr       m_uColor;
    bool                m_MSAA;
public:
    AmbientLight(const ShaderProgramPtr& prog, const InputLayoutPtr& layout, const VideoBufferPtr& fsq);
    virtual ~AmbientLight() {}

    virtual void render(Scene& scene, const TexturePtr& albedo, const TexturePtr& normal, const TexturePtr& depth);
protected:
    CE_DECLARE_ATTRIBUTE(Vector3, CE_RANGE_CLAMPED_POSITIVE, Color, "Color")

    CE_ATTRIBUTES(CE_POLYMORPHIC, GlobalLightSource,
               CE_CREATE_ATTRIBUTE(Color));
};

typedef shared_ptr<AmbientLight> AmbientLightPtr;

typedef Factory<LightSourcePtr> LightSourceFactory;
typedef shared_ptr<LightSourceFactory> LightSourceFactoryPtr;

class SpotlightFactory: public LightSourceFactory
{
    ShaderProgramPtr    m_Prog;
    InputLayoutPtr      m_Layout;
    VideoBufferPtr      m_FullScreenQuad;
public:
    SpotlightFactory(const ResourceManagerPtr& res_mgr);

    virtual LightSourcePtr create();
};

class PointLightFactory: public LightSourceFactory
{
    ShaderProgramPtr    m_Prog;
    InputLayoutPtr      m_Layout;
    VideoBufferPtr      m_FullScreenQuad;
public:
    PointLightFactory(const ResourceManagerPtr& res_mgr);

    virtual LightSourcePtr create();
};

class DirectionalLightFactory: public LightSourceFactory
{
    ShaderProgramPtr    m_Prog;
    InputLayoutPtr      m_Layout;
    VideoBufferPtr      m_FullScreenQuad;
public:
    DirectionalLightFactory(const ResourceManagerPtr& res_mgr);

    virtual LightSourcePtr create();
};

class AmbientLightFactory: public LightSourceFactory
{
    ShaderProgramPtr    m_Prog;
    InputLayoutPtr      m_Layout;
    VideoBufferPtr      m_FullScreenQuad;
public:
    AmbientLightFactory(const ResourceManagerPtr& res_mgr);

    virtual LightSourcePtr create();
};

class LightingManager: public FactoryManager<LightSourcePtr>
{
public:
    LightingManager(const ResourceManagerPtr& res_mgr);
};

typedef shared_ptr<LightingManager> LightingManagerPtr;
}

#endif /* LIGHTING_HH_ */
